#include <stdio.h>
#include <stdbool.h>
#include "z_logger.h"
#include "model.h"
#include "value.h"
#include "sched_pub.h"
#include "slave_handler.h"

int iec104_asdu_all_write(struct _pub_handler_t *handler, device_t *device, segment_e segment) {
    IMasterConnection connection = handler->handle;
    iec104_params_t *args = handler->args;

    CS101_AppLayerParameters app_layer_params = IMasterConnection_getApplicationLayerParameters(connection);
    CS101_ASDU asdu = CS101_ASDU_create(
            app_layer_params,
            true,
            args->cot,
            device->super.id,
            args->seqiot->default_uplink->rtu,
            false,
            false);

    vector_t *tag_list = &device->_product->thing_model[segment];
    vector_t *val_list = &device->thing_value[segment];

    for (int i = 0; i < vector_size(val_list); ++i) {
        thing_model_t *tag = vector_get(tag_list, i);
        value_t *value = vector_get(val_list, i);

        if (value->_changed && value->timestamp > TIME_20100101) {
            value->_changed = false;

            int tag_id = thing_model_tag_id(tag);
            log_trace("lru=%d, tag=%d, value=%f", device->super.id, tag_id, value->value);

            InformationObject io;
            if (args->with_timestamp) {
                CP56Time2a timestamp = CP56Time2a_createFromMsTimestamp(NULL, value->timestamp * 1000L);
                switch (tag->value_type) {
                    case vt_point:
                        io = (InformationObject) SinglePointWithCP56Time2a_create(NULL,
                                                                                  tag_id,
                                                                                  value->value,
                                                                                  IEC60870_QUALITY_GOOD,
                                                                                  timestamp);
                        break;
                    case vt_dpoint:
                        io = (InformationObject) DoublePointWithCP56Time2a_create(NULL,
                                                                                  tag_id,
                                                                                  value->value,
                                                                                  IEC60870_QUALITY_GOOD,
                                                                                  timestamp);
                        break;
                    case vt_normalized:
                    case vt_scaled:
                    case vt_floating:
                        io = (InformationObject) MeasuredValueShortWithCP56Time2a_create(
                                NULL,
                                tag_id,
                                value->value,
                                IEC60870_QUALITY_GOOD,
                                timestamp);
                        break;
                    case vt_totals:
                        io = (InformationObject) IntegratedTotalsWithCP56Time2a_create(NULL,
                                                                                       tag_id,
                                                                                       BinaryCounterReading_create(NULL,
                                                                                                                   value->value,
                                                                                                                   0,
                                                                                                                   false,
                                                                                                                   false,
                                                                                                                   false),
                                                                                       timestamp);
                        break;
                    default:
                        io = (InformationObject) MeasuredValueShortWithCP56Time2a_create(NULL,
                                                                                         tag_id,
                                                                                         value->value,
                                                                                         IEC60870_QUALITY_GOOD,
                                                                                         timestamp);
                        break;
                }
            } else {
                switch (tag->value_type) {
                    case vt_point:
                        io = (InformationObject) SinglePointInformation_create(NULL,
                                                                               tag_id,
                                                                               value->value,
                                                                               IEC60870_QUALITY_GOOD);
                        break;
                    case vt_dpoint:
                        io = (InformationObject) DoublePointInformation_create(NULL,
                                                                               tag_id,
                                                                               value->value,
                                                                               IEC60870_QUALITY_GOOD);
                        break;
                    case vt_normalized:
                    case vt_scaled:
                    case vt_floating:
                        io = (InformationObject) MeasuredValueShort_create(
                                NULL,
                                tag_id,
                                value->value,
                                IEC60870_QUALITY_GOOD);
                        break;
                    case vt_totals:
                        io = (InformationObject) IntegratedTotals_create(NULL,
                                                                         tag_id,
                                                                         BinaryCounterReading_create(NULL,
                                                                                                     value->value,
                                                                                                     0,
                                                                                                     false,
                                                                                                     false,
                                                                                                     false));
                        break;
                    default:
                        io = (InformationObject) MeasuredValueShort_create(
                                NULL,
                                tag_id,
                                value->value,
                                IEC60870_QUALITY_GOOD);
                        break;
                }
            }

            //容量满
            if (!CS101_ASDU_addInformationObject(asdu, io)) {
                //发送并且销毁旧ASDU
                IMasterConnection_sendASDU(connection, asdu);
                CS101_ASDU_destroy(asdu);

                //创建新ASDU
                asdu = CS101_ASDU_create(
                        app_layer_params,
                        true,
                        args->cot,
                        device->super.id,
                        args->seqiot->default_uplink->rtu,
                        false,
                        false);
                CS101_ASDU_addInformationObject(asdu, io);
            }

            InformationObject_destroy(io);
        }
    }
    //有数据才发送
    if (0 != CS101_ASDU_getNumberOfElements(asdu)) {
        IMasterConnection_sendASDU(connection, asdu);
    }
    CS101_ASDU_destroy(asdu);

    return 0;
}

int iec104_asdu_write(struct _pub_handler_t *handler, device_t *device, segment_e segment) {
    IMasterConnection connection = handler->handle;
    iec104_params_t *args = handler->args;

    CS101_AppLayerParameters app_layer_params = IMasterConnection_getApplicationLayerParameters(connection);
    CS101_ASDU asdu = CS101_ASDU_create(
            app_layer_params,
            false,
            args->cot,
            device->super.id,
            args->seqiot->default_uplink->rtu,
            false,
            false);

    vector_t *tag_list = &device->_product->thing_model[segment];
    vector_t *val_list = &device->thing_value[segment];

    for (int i = 0; i < vector_size(val_list); ++i) {
        thing_model_t *tag = vector_get(tag_list, i);
        value_t *value = vector_get(val_list, i);

        if (value->_changed && value->timestamp > TIME_20100101) {
            value->_changed = false;

            int tag_id = thing_model_tag_id(tag);
            log_trace("lru=%d, tag=%d, value=%f", device->super.id, tag_id, value->value);

            InformationObject io;
            if (args->with_timestamp) {
                CP56Time2a timestamp = CP56Time2a_createFromMsTimestamp(NULL, value->timestamp * 1000L);
                switch (tag->value_type) {
                    case vt_point:
                        io = (InformationObject) SinglePointWithCP56Time2a_create(NULL,
                                                                                  tag_id,
                                                                                  value->value,
                                                                                  IEC60870_QUALITY_GOOD,
                                                                                  timestamp);
                        break;
                    case vt_dpoint:
                        io = (InformationObject) DoublePointWithCP56Time2a_create(NULL,
                                                                                  tag_id,
                                                                                  value->value,
                                                                                  IEC60870_QUALITY_GOOD,
                                                                                  timestamp);
                        break;
                    case vt_normalized:
                    case vt_scaled:
                    case vt_floating:
                        io = (InformationObject) MeasuredValueShortWithCP56Time2a_create(
                                NULL,
                                tag_id,
                                value->value,
                                IEC60870_QUALITY_GOOD,
                                timestamp);
                        break;
                    case vt_totals:
                        io = (InformationObject) IntegratedTotalsWithCP56Time2a_create(NULL,
                                                                                       tag_id,
                                                                                       BinaryCounterReading_create(NULL,
                                                                                                                   value->value,
                                                                                                                   0,
                                                                                                                   false,
                                                                                                                   false,
                                                                                                                   false),
                                                                                       timestamp);
                        break;
                    default:
                        io = (InformationObject) MeasuredValueShortWithCP56Time2a_create(NULL,
                                                                                         tag_id,
                                                                                         value->value,
                                                                                         IEC60870_QUALITY_GOOD,
                                                                                         timestamp);
                        break;
                }
            } else {
                switch (tag->value_type) {
                    case vt_point:
                        io = (InformationObject) SinglePointInformation_create(NULL,
                                                                               tag_id,
                                                                               value->value,
                                                                               IEC60870_QUALITY_GOOD);
                        break;
                    case vt_dpoint:
                        io = (InformationObject) DoublePointInformation_create(NULL,
                                                                               tag_id,
                                                                               value->value,
                                                                               IEC60870_QUALITY_GOOD);
                        break;
                    case vt_normalized:
                    case vt_scaled:
                    case vt_floating:
                        io = (InformationObject) MeasuredValueShort_create(
                                NULL,
                                tag_id,
                                value->value,
                                IEC60870_QUALITY_GOOD);
                        break;
                    case vt_totals:
                        io = (InformationObject) IntegratedTotals_create(NULL,
                                                                         tag_id,
                                                                         BinaryCounterReading_create(NULL,
                                                                                                     value->value,
                                                                                                     0,
                                                                                                     false,
                                                                                                     false,
                                                                                                     false));
                        break;
                    default:
                        io = (InformationObject) MeasuredValueShort_create(
                                NULL,
                                tag_id,
                                value->value,
                                IEC60870_QUALITY_GOOD);
                        break;
                }
            }

            //容量满
            if (!CS101_ASDU_addInformationObject(asdu, io)) {
                //发送并且销毁旧ASDU
                IMasterConnection_sendASDU(connection, asdu);
                CS101_ASDU_destroy(asdu);

                //创建新ASDU
                asdu = CS101_ASDU_create(
                        app_layer_params,
                        false,
                        args->cot,
                        device->super.id,
                        args->seqiot->default_uplink->rtu,
                        false,
                        false);
                CS101_ASDU_addInformationObject(asdu, io);
            }

            InformationObject_destroy(io);
        }
        //有数据才发送
        if (0 != CS101_ASDU_getNumberOfElements(asdu)) {
            IMasterConnection_sendASDU(connection, asdu);
        }
        CS101_ASDU_destroy(asdu);
        asdu = CS101_ASDU_create(
                app_layer_params,
                false,
                args->cot,
                device->super.id,
                args->seqiot->default_uplink->rtu,
                false,
                false);
    }

    CS101_ASDU_destroy(asdu);

    return 0;
}

int iec104_asdu_flush(struct _pub_handler_t *handler) {
    return 0;
}

bool interrogation_handler(void *parameter, IMasterConnection connection, CS101_ASDU asdu, uint8_t qoi) {
    log_debug("Received interrogation for group %i", qoi);

    if (qoi == CS101_COT_INTERROGATED_BY_STATION) {
        IMasterConnection_sendACT_CON(connection, asdu, false);

        iec104_params_t args = {
                .seqiot = parameter,
                .sched_time = 0,
                .cot = CS101_COT_ACTIVATION_CON,
                .with_timestamp = false,
        };
        pub_handler_t pub_handler = {
                .handle = connection,
                .args = &args,
                .on_filter = all_call_filter,
                .on_compare = true_compare,
                .on_write = iec104_asdu_all_write,
                .on_flush = iec104_asdu_flush,
        };

        check_whole(&pub_handler, args.seqiot);
        publish_changed(&pub_handler, args.seqiot);

        IMasterConnection_sendACT_TERM(connection, asdu);
    } else {
        IMasterConnection_sendACT_CON(connection, asdu, true);
    }

    return true;
}

bool counter_interrogation_handler(void *parameter, IMasterConnection connection, CS101_ASDU asdu, QualifierOfCIC qcc) {
    log_debug("Received counter interrogation for group %i", qcc);

    if (CS101_COT_ACTIVATION == CS101_ASDU_getCOT(asdu)) {
        IMasterConnection_sendACT_CON(connection, asdu, false);

        iec104_params_t args = {
                .seqiot = parameter,
                .sched_time = 0,
                .cot = CS101_COT_ACTIVATION_CON,
                .with_timestamp = false,
        };
        pub_handler_t pub_handler = {
                .handle = connection,
                .args = &args,
                .on_filter = totals_filter,
                .on_compare = true_compare,
                .on_write = iec104_asdu_write,
                .on_flush = iec104_asdu_flush,
        };

        check_whole(&pub_handler, args.seqiot);
        publish_changed(&pub_handler, args.seqiot);

        IMasterConnection_sendACT_TERM(connection, asdu);
    } else {
        IMasterConnection_sendACT_CON(connection, asdu, true);
    }

    return true;
}